extern "C" {
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
}

#include <stdlib.h>
#include <string.h>

#include "lua_package.h"

#define LUA_STG_PACKAGELIB  "package"
#define LUA_PACKAGENAME  "package.table"

typedef unsigned char  pkg_byte;

struct lua_stg_package
{
    lua_State * L;
    void const* value;
    size_t length;
    size_t offset;
    size_t wsize;
    size_t alloced;
    pkg_byte * wbuf;
};

static int create_package(lua_State * L)
{
    void const * value = luaL_opt(L,lua_topointer,1,0);
    size_t len = luaL_opt(L,lua_tointeger,2,0);
    lua_stg_package * pkg = (lua_stg_package*)lua_newuserdata(L, sizeof(lua_stg_package));
    pkg->value = value;
    pkg->length= size_t(len);
    pkg->offset = 0;
    pkg->wsize =0;
    pkg->alloced=0;
    pkg->wbuf = 0;
    luaL_getmetatable(L,LUA_PACKAGENAME);
    lua_setmetatable(L, -2);
    return 1;
}
static lua_stg_package * check_package(lua_State *L, int idx) {
    lua_stg_package * pkg = (lua_stg_package*)luaL_checkudata(L, idx,LUA_PACKAGENAME);
    luaL_argcheck(L, pkg, idx, "package expected");
    return pkg;
}

static int package_gc(lua_State * L)
{
    lua_stg_package * pkg = check_package(L,1);
    if(pkg->wbuf){
	free(pkg->wbuf);
    }
    pkg->wbuf = 0;
    pkg->wsize=0;
    pkg->alloced = 0;
    pkg->value =0;
    pkg->offset = 0;
    pkg->length = 0;
    return 0;
}

static int package_reset(lua_State * L)
{
    lua_stg_package * pkg = check_package(L,1);
    void const * value = luaL_opt(L,lua_topointer,2,0);
    size_t len = luaL_opt(L,lua_tointeger,3,0);
    pkg->value = value;
    pkg->length = len;
    pkg->wsize = 0;
    pkg->offset = 0;
    return 0;
}

static int package_tostring(lua_State * L)
{
    lua_stg_package * pkg = check_package(L,1);
    if(pkg->wbuf){
	luaL_argcheck(L,(pkg->wsize > 0),1, "package write size <=0");
	lua_pushlstring(L,(char const*)pkg->wbuf,pkg->wsize);
    }
    else{
	lua_pushboolean(L,false);
    }
    return 1;
}

static int package_get_raw_wdata(lua_State * L)
{
    lua_stg_package * pkg = check_package(L,1);
    if(pkg->wbuf){
	luaL_argcheck(L,(pkg->wsize > 0),1, "package write size <=0");
	lua_pushlightuserdata(L,(void *)pkg->wbuf);
    }   
    else{
	lua_pushnil(L);
    }   
    return 1;
}
static int package_get_raw_rdata(lua_State *L)
{
    lua_stg_package * pkg = check_package(L,1);
    if(pkg->value){
	luaL_argcheck(L,pkg->value,1, "package value is null");
	lua_pushlightuserdata(L,const_cast<void*>(pkg->value));
    }   
    else{
	lua_pushnil(L);
    }   
    return 1;
}

struct cstring{};

template<typename T>
struct PkgReader
{
    template<typename Pusher>
    PkgReader(lua_State * L,lua_stg_package * pkg,Pusher pusher)
    {
	if(!pkg->value || pkg->length ==0 || pkg->offset + sizeof(T) > pkg->length){
            lua_pushnil(L);
	}
	else{
	    T const * fv = (T const*)((pkg_byte const*)pkg->value + pkg->offset);
	    luaL_argcheck(L,fv,1, "field value can't be null");
	    pkg->offset += sizeof(T);
	    pusher(L,*fv);
	}
    }
};

template<>
struct PkgReader<cstring>
{
    template<typename Pusher>
    PkgReader(lua_State * L,lua_stg_package * pkg,Pusher pusher)
    {
	if(!pkg->value || pkg->length ==0){
	    lua_pushnil(L);
	}
	else{
	    if(pkg->offset + sizeof(unsigned int) > pkg->length){
		lua_pushnil(L);
		return;
	    }
	    unsigned int * sz = (unsigned int*)((pkg_byte*)pkg->value + pkg->offset);
	    pkg->offset += sizeof(unsigned int);
	    if(pkg->offset + *sz  > pkg->length ){
		lua_pushnil(L);
		return;
	    }
	    char const * str = (char const*)((pkg_byte*)pkg->value + pkg->offset);
	    luaL_argcheck(L,str,1, "field value can't be null");
	    pkg->offset += *sz;
	    lua_pushlstring(L,str,*sz);
	}
    }
};


#define DEF_PKG_READER(type,pusher)\
static int package_read_##type(lua_State * L)\
{\
    lua_stg_package * pkg = check_package(L,1);\
    PkgReader<type> reader(L,pkg,lua_push##pusher);\
    return 1;\
}

template<typename T>
struct PkgWriter
{
    template<typename Getter>
    PkgWriter(lua_State * L,lua_stg_package * pkg,Getter getter)
    {
	T v = getter(L,2);
	memcpy(static_cast<void*>(pkg->wbuf+pkg->wsize),&v,sizeof(T));
	pkg->wsize += sizeof(T);
    }
};

template<>
struct PkgWriter<cstring>
{
    template<typename Getter>
    PkgWriter(lua_State * L,lua_stg_package * pkg,Getter getter)
    {
	unsigned int sz=0;
	char const * str = luaL_checklstring(L,2,(size_t*)&sz);
	memcpy(static_cast<void*>(pkg->wbuf+pkg->wsize),&sz,sizeof(unsigned int));
	pkg->wsize += sizeof(unsigned int);
	memcpy(static_cast<void*>(pkg->wbuf+pkg->wsize),str,sz);
	pkg->wsize += sz;
    }
};

#define DEF_PKG_WRITER(type,getter)\
    static int package_write_##type(lua_State * L)\
{\
    lua_stg_package * pkg = check_package(L,1);\
    if(pkg->wsize + sizeof(type) > pkg->alloced ){\
	pkg->alloced = (pkg->wsize) * 2 + sizeof(type);\
	pkg_byte * newbuf = (pkg_byte*)malloc(pkg->alloced);\
        memcpy(newbuf,pkg->wbuf,pkg->wsize);\
        if(pkg->wbuf){\
	    free(pkg->wbuf);\
	}\
	pkg->wbuf = newbuf;\
    }\
    PkgWriter<type> writter(L,pkg,lua_to##getter);\
    return 0;\
}

DEF_PKG_READER(long,number)
DEF_PKG_READER(int,integer)
DEF_PKG_READER(short,integer)
DEF_PKG_READER(pkg_byte,integer)
DEF_PKG_READER(bool,boolean)
DEF_PKG_READER(float,number)
DEF_PKG_READER(double,number)
DEF_PKG_READER(cstring,lstring)

DEF_PKG_WRITER(long,number)
DEF_PKG_WRITER(int,integer)
DEF_PKG_WRITER(short,integer)
DEF_PKG_WRITER(pkg_byte,integer)
DEF_PKG_WRITER(bool,boolean)
DEF_PKG_WRITER(float,number)
DEF_PKG_WRITER(double,number)
DEF_PKG_WRITER(cstring,lstring)

static const struct luaL_reg package_methods[] = {
    {"readLong", package_read_long},
    {"readInt", package_read_int},
    {"readShort", package_read_short},
    {"readByte", package_read_pkg_byte},
    {"readBoolean", package_read_bool},
    {"readFloat", package_read_float},
    {"readDouble", package_read_double},
    {"readString", package_read_cstring},
    {"writeLong", package_write_long},
    {"writeInt", package_write_int},
    {"writeShort", package_write_short},
    {"writeByte", package_write_pkg_byte},
    {"writeBoolean", package_write_bool},
    {"writeFloat", package_write_float},
    {"writeDouble", package_write_double},
    {"writeString", package_write_cstring},
    {"__tostring", package_tostring},
    {"reset", package_reset},
    {"getRawWriteData", package_get_raw_wdata},
    {"getRawReadData", package_get_raw_rdata},
    {"__gc", package_gc},
    {NULL, NULL}
};

static const struct luaL_reg stg_pkg_libfuncs[] = {
    {"new", create_package},
    {NULL, NULL}
};

LUALIB_API int luaopen_package(lua_State *L)
{    
    luaL_newmetatable(L,LUA_PACKAGENAME);
    lua_pushliteral(L, "__index");
    lua_pushvalue(L, -2);
    lua_rawset(L, -3);
    luaL_openlib (L, 0,package_methods , 0);
    luaL_openlib (L,LUA_STG_PACKAGELIB,stg_pkg_libfuncs , 0);	
    return 1;
}
